/**
 * 
 * @Project le cOeur - Data Virtualization
 * @Date 08.12.2012
 * @Author Aebi Pascal, Kocher Marc
 * @School BFH-TI Biel, Bachelor Thesis
 *
 */

package main.framework.representation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;

import main.framework.exception.NoRepresentationCreatorFoundException;
import main.framework.http.ContentType;
import main.framework.http.MessageHeader;
import main.framework.http.MessageHeaderElement;
import main.framework.http.MessageHeaderElementParameter;

/**
 * This class represents the manager for the handling of the different representation creators.
 */
public class RepresentationCreatorManager {
	
	private ArrayList<RepresentationCreator> representationCreators;
	private TextRepresentationCreator fallBackRepresentationCreator;
	private RepresentationCreator defaultRepresentationCreator;
	private Logger logger;
	
	/**
	 * Creates a manager for the handling of the different representation creators.
	 */
	public RepresentationCreatorManager() {
		this.representationCreators = new ArrayList<>();
		this.fallBackRepresentationCreator = new TextRepresentationCreator(new ContentType("text/plain"));
		this.representationCreators.add(fallBackRepresentationCreator);
		this.defaultRepresentationCreator = this.fallBackRepresentationCreator;
		this.logger = Logger.getLogger("SysLogger");
	}
	
	/**
	 * Makes an existing representation creator available for the application.
	 * 
	 * @param representationCreator The representation creator to attach.
	 * @param isDefault Defines if the representation creator should be the default one.
	 */
	public void attachRepresentationCreator(RepresentationCreator representationCreator, boolean isDefault) {
		if(!this.representationCreators.contains(representationCreator)){
			this.representationCreators.add(representationCreator);
		}
		if(isDefault){
			this.defaultRepresentationCreator = representationCreator;
		}
	}

	/**
	 * Makes an existing representation creator unavailable for the application.
	 * 
	 * @param contentType The content type of the corresponding representation creator which should be unavailable.
	 */
	public void detachRepresentationCreator(String contentType) {
		boolean foundRepresentationCreator = false;
		List<RepresentationCreator> representationCreators = new ArrayList<>();
		representationCreators.addAll(this.representationCreators);
		for (RepresentationCreator representationCreator: representationCreators) {
			if(representationCreator.getContentType().getName().equalsIgnoreCase(contentType)){
				this.representationCreators.remove(representationCreator);
				if(representationCreator.equals(this.defaultRepresentationCreator)){
					this.defaultRepresentationCreator = this.fallBackRepresentationCreator;
				}
				foundRepresentationCreator = true;
				break;
			}
		}
		if(!foundRepresentationCreator){
			this.logger.warning("No matching representation creator for the given content type found.");
		}
	}
	
	/**
	 * Gets all existing representation creators.
	 * 
	 * @return All existing representation creators.
	 */
	public List<RepresentationCreator> getRepresentationCreators() {
		return this.representationCreators;
	}
	
	/**
	 * Gets the representation creator which is defined as default.
	 * 
	 * @return The default representation creator.
	 */
	public synchronized RepresentationCreator getDefaultRepresentationCreator() {
		return this.defaultRepresentationCreator;
	}
	
	/**
	 * Gets the representation creator which is defined as fallback.
	 * 
	 * @return The fallback representation creator.
	 */
	public synchronized TextRepresentationCreator getFallbackRepresentationCreator() {
		return this.fallBackRepresentationCreator;
	}
	
	/**
	 * Gets a representation creator by means of a given content type.
	 * 
	 * @param contentType The content Type of the corresponding representation creator which should be returned.
	 * @return The representation creator corresponding to the given content Type.
	 * @throws NoRepresentationCreatorFoundException If no representation creator is found for the given content type.
	 */
	public RepresentationCreator getRepresentationCreator(ContentType contentType) throws NoRepresentationCreatorFoundException {
		List<RepresentationCreator> representationCreators = this.searchRepresentationCreator(contentType);
		if(!representationCreators.isEmpty()){
			return representationCreators.get(0);
		}
		else{
			throw new NoRepresentationCreatorFoundException("No representation creator for the given content type found.");
		}
	}
	
	/**
	 * Gets a representation creator by means of a given message header.
	 * 
	 * @param accept The message header which contains the accepted content types.
	 * @return The representation creator corresponding to the accepted content types in the message header.
	 * @throws NoRepresentationCreatorFoundException If no representation creator is found for the given content type.
	 */
	public RepresentationCreator getRepresentationCreator(MessageHeader accept) throws NoRepresentationCreatorFoundException {
		List<ContentType> acceptedContentTypes = new ArrayList<>();
		List<RepresentationCreator> representationCreators = new ArrayList<>();
		// Create content types and sort by value
		for (MessageHeaderElement acceptedContentType : accept.getFieldValueElements()) {
			acceptedContentTypes.add(new ContentType(acceptedContentType.getName(), acceptedContentType.getValue(), acceptedContentType.getParameters()));
		}
		Collections.sort(acceptedContentTypes);
		// Search a matching representation creator
		for (ContentType contentType : acceptedContentTypes) {
			representationCreators.addAll(this.searchRepresentationCreator(contentType));
			if(!representationCreators.isEmpty()){
				return representationCreators.get(0);
			}
		}
		throw new NoRepresentationCreatorFoundException("No representation creator for the given content type found.");
	}
	
	/**
	 * Searches possible representation creators by means of a given content type.
	 * 
	 * @param contentType The content type for which all possible representation creators should be returned.
	 * @return A list containing all found representation creators.
	 */
	private List<RepresentationCreator> searchRepresentationCreator(ContentType contentType) {
		List<RepresentationCreator> possibleRepresentationCreators = new ArrayList<>();
		List<RepresentationCreator> processableRepresentationCreators = new ArrayList<>();
		String[] types = contentType.getName().split("/");
		String type = types[0];
		if(type.equalsIgnoreCase("*")){
			possibleRepresentationCreators.add(this.defaultRepresentationCreator);
		}
		else{
			// Check type
			for (RepresentationCreator representationCreator : this.representationCreators) {
				ContentType supportedContentType = representationCreator.getContentType();
				if(type.equalsIgnoreCase(supportedContentType.getType())){
					possibleRepresentationCreators.add(representationCreator);
				}
			}
			// Check sub type
			if(types.length == 2){
				String subType = types[1];
				processableRepresentationCreators.addAll(possibleRepresentationCreators);
				for (RepresentationCreator representationCreator: processableRepresentationCreators) {
					ContentType supportedContentType = representationCreator.getContentType();
					if(!subType.equalsIgnoreCase("*") && !subType.equalsIgnoreCase(supportedContentType.getSubType())){
						possibleRepresentationCreators.remove(representationCreator);
					}
				}
				processableRepresentationCreators.clear();
			}
			// Check parameters
			List<MessageHeaderElementParameter> parameters = contentType.getParameters();
			if(!parameters.isEmpty()){
				processableRepresentationCreators.addAll(possibleRepresentationCreators);
				for (RepresentationCreator representationCreator: processableRepresentationCreators) {
					ContentType supportedContentType = representationCreator.getContentType();
					for (MessageHeaderElementParameter parameter : parameters) {
						if(!parameter.getName().equalsIgnoreCase("q") && !supportedContentType.getParameters().contains(parameter)){
							possibleRepresentationCreators.remove(representationCreator);
						}
					}
				}
				processableRepresentationCreators.clear();
			}
		}
		return possibleRepresentationCreators;
	}
	
}
